Commit-Reveal Scheme 設法達到以下兩個特性:
假設你知道某支股票下個月會漲的消息,但基於法律問題你不能告訴你的朋友,但你又想向朋友證明你知道會漲(傲慢作祟)。那你可以將「某股下個月會漲」這串字進行雜湊 (commit),將亂碼交給你的朋友,一個月後,股票真的漲了,再將原字串告訴你的朋友(reveal),你的朋友用同樣的雜湊函數得到的結果跟一個月前給的亂碼一樣,你朋友相信你是真的知道股票會漲。
上面例子出自 Real-World Cryptography by David Wong (p.32 - 2.4.1)
Solidity 文件的範例二介紹如何實作彌封拍賣(sealed-bid auction),它的原理就是使用 commitment scheme。彌封拍賣是指競標者彼此不知道別人的出價下競標。
首先合約至少要區分 commit phase 和 reveal phase 兩個階段。commit 階段用戶要在鏈下對一筆資料進行雜湊:keccak256(abi.encodePacked(value, fake, secret))
,會得到 32-bytes hash (or digest),然後呼叫 bid()
,就是將你的 commitment 放上鏈。
function bid(bytes32 blindedBid) external payable onlyBefore(biddingEnd) {
bids[msg.sender].push(Bid({blindedBid: blindedBid, deposit: msg.value}));
}
等到 reveal 階段,每個參與者要公佈當初雜湊的 input (or pre-image),呼叫 reveal
後會在鏈上進行雜湊,確認符合之前的 commitment,然後將當前最高價格儲存起來,之後每個人 reveal 都會比對並更新最高價格。
function reveal(uint256[] calldata values, bool[] calldata fakes, bytes32[] calldata secrets) external onlyAfter(biddingEnd) onlyBefore(revealEnd)
雜湊的資料由三個元素組成:
bid()
的同時你要投入大於等於 value
的值。reveal()
要設計退款機制。競標者會不會有動機出價後不 reveal?因為出價時必須投入 ETH 到合約,如果你不 reveal,合約可以藉此沒收你的 ETH 作為懲罰。
要設計剪刀石頭布的遊戲也需要 Commit-Reveal Scheme。
為了避免某個人先呼叫合約就把自己要出的拳公開在鏈上,我們要先對出拳進行 commit,把 commitment 存在合約,等到 reveal 階段時,第二個玩家揭露後就能跟第一個玩家比較,然後分出勝負。
玩家有沒有動機不 reveal?當玩家 A 看到玩家 B reveal 剪刀,玩家 A 知道自己輸定了,決定不 reveal,於是合約只能宣判玩家 A 棄賽,但只要玩家 A 不 reveal,除了 A 之外沒人真的知道是誰輸誰贏(儘管我們心理都知道)。解決辦法是強迫兩個玩家 commit 之前先抵押一定額度的 ETH,reveal 後才能領回,作為鼓勵 reveal 的誘因。
PS: zk 剪都石頭布
如果投票要求結果出爐也必須保護投票者匿名,那就不能用 commitment scheme,因為最後需要 reveal。
如果只需要在投票階段匿名,reveal 時公佈各自投得票,那面臨不 reveal 的狀況怎麼辦?要看表決的決策對投票者的影響有多大,如果票數對決策有重大影響,就算多數決仍輸,不 reveal 反而比較好的話,可能就算抵押了代幣,寧願損失抵押品也不願 reveal。
MACI 是一個匿名且防買票的投票系統,它防買票的設計建立在投票者無法向別人證明他投給誰。但 MACI 的缺點是中心化的計票者,計票者記得票一定正確,但計票者可以知道別人的票投給誰,買票者可以賄賂計票者而得知別人的票。因此計票者必須是可以被所有人信任的人。(目前的理解是這樣,若有錯請不吝糾正。)
最後附上有趣影片,主要是片頭前五分鐘表演次價拍賣中不 reveal 的搞笑情境:
https://www.youtube.com/watch?v=amUP8EZ8Z0w
影片中只有兩個人在玩次價拍賣,次價拍賣是出價最高者獲勝,但只需要付第二高價來獲得商品。兩個人都 commit 以後,第一個人 reveal,第二個人看到別人的出價決定不 reveal,於是整場拍賣沒有第二高價,唯一出價的人要用最高價買下商品 XD